#include "ad7190.h"
#include "stm32f4xx_hal.h"

extern UART_HandleTypeDef huart1;

typedef union {
    uint32_t U32;
    uint8_t U8[4];
}IOData;

/**
 * @define
 * @{
 */
#define STATUS_REG 0x00
#define CONFIG_REG 0x10
#define MODE_REG   0x08
#define DATA_REG   0x18
#define COMM_REG   0xFF 
#define REGISTER_LENGTH(_register_)                                         \
    ((_register_ == DATA_REG) ? 4 : (                                       \
        (_register_ == CONFIG_REG || _register_ == MODE_REG) ? 3 : (        \
            (_register_ == COMM_REG || _register_ == STATUS_REG) ? 1 : 0 )))
#define __GET_DOUT()   HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)
/**
 * @}
 */
 
/**
 * @private
 * @{
 */
__STATIC_INLINE void _change_edian(IOData *srcPtr) {
    uint8_t tmp   = srcPtr->U8[0];
    srcPtr->U8[0] = srcPtr->U8[3];
    srcPtr->U8[3] = tmp;
    tmp = srcPtr->U8[1];
    srcPtr->U8[1] = srcPtr->U8[2];
    srcPtr->U8[2] = tmp;
}    
    
__STATIC_INLINE HAL_StatusTypeDef _reset(AD7190_HandleTypeDef *had7190) {
    uint8_t cmd[6U] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU};
    return HAL_SPI_Transmit(had7190->SPI_Handle, cmd, 6, 10);
}    
    
__STATIC_INLINE HAL_StatusTypeDef _set_writing_reg(AD7190_HandleTypeDef *had7190, uint8_t toReg) {
    return HAL_SPI_Transmit(had7190->SPI_Handle, &toReg, 1, 10);
}

__STATIC_INLINE HAL_StatusTypeDef _write_to_reg(AD7190_HandleTypeDef *had7190, uint8_t *TxPtr, uint8_t toReg) {
    return HAL_SPI_Transmit(had7190->SPI_Handle, TxPtr, REGISTER_LENGTH(toReg), 10);
}

__STATIC_INLINE HAL_StatusTypeDef _set_reading_reg(AD7190_HandleTypeDef *had7190, uint8_t fromReg) {
	uint8_t tmp = fromReg | (1 << 6);
    return HAL_SPI_Transmit(had7190->SPI_Handle, &tmp, 1, 10);
}

__STATIC_INLINE HAL_StatusTypeDef _read_from_reg(AD7190_HandleTypeDef *had7190, uint8_t *RxPtr, uint8_t fromReg) {
    uint32_t cmd = 0U;
    return HAL_SPI_TransmitReceive(had7190->SPI_Handle, (uint8_t*)&cmd, RxPtr, REGISTER_LENGTH(fromReg), 10);
}

__STATIC_INLINE HAL_StatusTypeDef _AD7190_Write(AD7190_HandleTypeDef *had7190, uint8_t *TxPtr, uint8_t toReg)
{    
    HAL_StatusTypeDef status = _set_writing_reg(had7190, toReg);
    if(status != HAL_OK) 
    {
        return status;
    }
    return _write_to_reg(had7190, TxPtr, toReg);
}

__STATIC_INLINE HAL_StatusTypeDef _AD7190_Read(AD7190_HandleTypeDef *had7190, uint8_t *RxPtr, uint8_t fromReg)
{    
    HAL_StatusTypeDef status = _set_reading_reg(had7190, fromReg);
    if(status != HAL_OK) 
    {
        return status;
    }
    return _read_from_reg(had7190, RxPtr, fromReg);
}

/**
 * @}
 */

/**
 * @public
 * @{
 */
uint32_t HAL_AD7190_Step(AD7190_HandleTypeDef *had7190, uint32_t timeout) {
	HAL_StatusTypeDef status = HAL_OK;
	uint32_t tickStart = HAL_GetTick();
    IOData tmp;
    // lock
    __HAL_LOCK(had7190);
    had7190->State = HAL_AD7190_STATE_BUSY;
    // check spi
    if(had7190->SPI_Handle->State != HAL_SPI_STATE_READY) {
        had7190->ErrorCode = HAL_AD7190_ERROR_HARDWARE;
        goto error;
    }
    // wait data available
    while(__GET_DOUT()) { 
		if(HAL_GetTick() - tickStart >= timeout) {
            had7190->ErrorCode = HAL_AD7190_ERROR_TIMEOUT;
            goto error;
		}
	}
    // read data
    status = _AD7190_Read(had7190, tmp.U8, DATA_REG);
    
    if(status != HAL_OK) {
        had7190->ErrorCode = HAL_AD7190_ERROR_READ;
        goto error;
    }
    
error:
    had7190->State = HAL_AD7190_STATE_READY;
    __HAL_UNLOCK(had7190);
//    _change_edian(&tmp);
    return tmp.U32;
}


HAL_StatusTypeDef HAL_AD7190_Init(AD7190_HandleTypeDef *had7190) {
    HAL_StatusTypeDef status = HAL_OK;
    IOData config;
    uint8_t RxBuf[4U] = {0U};
    // check not null
    if(had7190 == NULL) {
        return HAL_ERROR;
    }
    // check spi
    if(had7190->SPI_Handle->State == HAL_SPI_STATE_RESET) {
        HAL_SPI_Init(had7190->SPI_Handle);
    }
    if(had7190->SPI_Handle->State != HAL_SPI_STATE_READY) {
        had7190->ErrorCode = HAL_AD7190_ERROR_HARDWARE;
		return HAL_ERROR;
    }
    // reset ad7190
    had7190->State = HAL_AD7190_STATE_BUSY;
    status = _reset(had7190);
    if(status != HAL_OK)
    {
        had7190->ErrorCode = HAL_AD7190_ERROR_READ;
        had7190->State  = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }
    // check reset status
    status = _AD7190_Read(had7190, RxBuf, STATUS_REG); 
    if(status != HAL_OK)
    {
        had7190->ErrorCode = HAL_AD7190_ERROR_READ;
        had7190->State  = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }
    if(RxBuf[0U] != 0x80U)
    {
        had7190->ErrorCode = HAL_AD7190_ERROR_HARDWARE;
        had7190->State  = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }
    // write configuration
    config.U32 = 0U;
    config.U32 |= had7190->init.Channel  << 8U;                       
    config.U32 |= had7190->init.Gain     << 0U;                       
    config.U32 |= had7190->init.Polarity << 3U;  
    _change_edian(&config);  
    status = _AD7190_Write(had7190, config.U8 + 1, CONFIG_REG);
    if(status != HAL_OK) 
    {
        had7190->ErrorCode = HAL_AD7190_ERROR_CONFIG;
        had7190->State  = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }

    // write mode
    config.U32 = 0U;
    config.U32 |= 1U << 20U;  
    config.U32 |= 1U << 19U;                          
    config.U32 |= had7190->init.OutputRate;
    _change_edian(&config);
    status = _AD7190_Write(had7190, config.U8 + 1, MODE_REG);
    if(status != HAL_OK)
    {
        had7190->ErrorCode = HAL_AD7190_ERROR_MODE;
        had7190->State = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }
    
    // test communication
    HAL_Delay(200);
    config.U32 = HAL_AD7190_Step(had7190, 10);
    _change_edian(&config);
    if(had7190->ErrorCode != HAL_AD7190_ERROR_NONE)
    {

        had7190->ErrorCode = HAL_AD7190_ERROR_READ;
        had7190->State  = HAL_AD7190_STATE_ERROR;
        return HAL_ERROR;
    }
    // initialize success
    had7190->ErrorCode = HAL_AD7190_ERROR_NONE;
    had7190->State     = HAL_AD7190_STATE_READY;    
    return HAL_OK;
}

HAL_StatusTypeDef HAL_AD7190_Deinit(AD7190_HandleTypeDef *had7190) {
    // check not null
    if(had7190 == NULL) {
        return HAL_ERROR;
    }
    // reset ad7190's state
    had7190->ErrorCode = HAL_AD7190_ERROR_NONE;
    had7190->State = HAL_AD7190_STATE_RESET;
    // unlock
    __HAL_UNLOCK(had7190);
    return HAL_OK;
}
/**
 * @}
 */
